﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.Xml;
using System.Web.Services.Description;

namespace Sawsdl.ServiceModel
{    
    public class SawsdlBehavior : IWsdlExportExtension, IEndpointBehavior
    {
        #region Fields

        private static readonly XmlDocument TempDocument = new XmlDocument();

        private const string SawsdlPrefix = "sawsdl";
        private const string SawsdlNamespace = "http://www.w3.org/ns/sawsdl";
        private const string ModelReferenceTagName = "modelReference";

        #endregion

        #region IWsdlExportExtension Members

        public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
        {            
        }

        public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
        {
            WsdlContractConversionContext contractConversionContext = context.ContractConversionContext;

            if (contractConversionContext.Contract != null)
            {

                // Setup namespace of the SAWSDL
                contractConversionContext.WsdlPortType.ServiceDescription.Namespaces.Add(SawsdlBehavior.SawsdlPrefix, SawsdlBehavior.SawsdlNamespace);

                // Process operations
                foreach (OperationDescription operation in contractConversionContext.Contract.Operations)
                {
                    SawsdlBehavior.ProcessOperation(contractConversionContext, operation);
                }

                SawsdlBehavior.ProcessInterface(contractConversionContext);                
            }
        }

        private static void ProcessInterface(WsdlContractConversionContext contractConversionContext)
        {
            SawsdlModelReferenceAttribute[] attributes = (SawsdlModelReferenceAttribute[])contractConversionContext.Contract.ContractType.GetCustomAttributes(
            typeof(SawsdlModelReferenceAttribute), true
            );

            if (attributes.Length > 0)
            {
                SawsdlBehavior.AppendModelReferenceAttribute(
                    contractConversionContext.WsdlPortType,
                    attributes[0].ReferenceValue
                    );
            }
        }

        private static void ProcessOperation(WsdlContractConversionContext context, OperationDescription operation)
        {
            SawsdlModelReferenceAttribute[] attributes = (SawsdlModelReferenceAttribute[])operation.SyncMethod.GetCustomAttributes(
                typeof(SawsdlModelReferenceAttribute), true
                );                       

            if (attributes.Length > 0)
            {
                // Meantime WCF donot support WSDL 2.0.
                // Also even if WCF support attribute extensions on operation, WSDL 1.0 don't.
                // To correct this specification intriduces attrExtensions
                // This is code for WSDL 2.0
                //SawsdlBehavior.AppendModelReferenceAttribute(
                //    context.GetOperationBinding(operation),
                //    attributes[0].ReferenceValue
                //    );
                // This is code for WSDL 1.0                

                XmlElement attrExtensionsElement =  SawsdlBehavior.TempDocument.CreateElement(
                    SawsdlBehavior.SawsdlPrefix,
                    "attrExtensions",
                    SawsdlBehavior.SawsdlNamespace
                    );

                XmlAttribute modelReferenceAttribute = SawsdlBehavior.CreateModelReferenceAttribute(
                    attributes[0].ReferenceValue
                    );
                attrExtensionsElement.Attributes.Append(modelReferenceAttribute);

                context.GetOperation(operation).Extensions.Add(attrExtensionsElement);
            }
        }        
        

        private static void AppendModelReferenceAttribute(DocumentableItem item, string modelReference)
        {
            XmlAttribute modelReferenceAttribute = SawsdlBehavior.CreateModelReferenceAttribute(modelReference);
            SawsdlBehavior.AppendExtesionAttribute(item, modelReferenceAttribute);
        }

        private static XmlAttribute CreateModelReferenceAttribute(string modelReference)
        {
            XmlAttribute modelReferenceAttribute = SawsdlBehavior.CreateAttribute(
                SawsdlBehavior.SawsdlPrefix,
                SawsdlBehavior.ModelReferenceTagName,
                SawsdlBehavior.SawsdlNamespace,
                modelReference
                );
            return modelReferenceAttribute;
        }

        private static void AppendExtesionAttribute(DocumentableItem item, XmlAttribute attribute)
        {
            XmlAttribute[] attributes = item.ExtensibleAttributes;
            if (attributes != null)
            {
                int length = attributes.Length;
                XmlAttribute[] resultAttributes = new XmlAttribute[length + 1];
                Array.Copy(attributes, resultAttributes, length);
                attributes[length] = attribute;
            }
            else
            {
                attributes = new XmlAttribute[1] { attribute };
            }

            item.ExtensibleAttributes = attributes;

        }

        private static XmlAttribute CreateAttribute(string prefix, string name, string namespaceUri, string value)
        {
            XmlAttribute attribute = SawsdlBehavior.TempDocument.CreateAttribute(prefix, name, namespaceUri);
            attribute.Value = value;
            return attribute;
        }

        #endregion

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            // not used
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            // not used
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            // not used
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            // not used
        }

        #endregion        
    }
}
